package ualrcdg.server;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.ServerSocket;
import java.util.ArrayList;
import java.util.Arrays;

import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.TransformerException;

import org.w3c.dom.Document;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

import ualrcdg.shared.XMLHelper;
import ualrcdg.shared.exceptions.CDGException;
import ualrcdg.shared.exceptions.CDGParseException;
import ualrcdg.shared.requests.AuthenticatePlayerRequest;
import ualrcdg.shared.requests.FetchGameRequest;
import ualrcdg.shared.requests.GameListRequest;
import ualrcdg.shared.requests.RegisterPlayerRequest;
import ualrcdg.shared.requests.UpdateProgressRequest;
import ualrcdg.shared.requests.ValidateRuleRequest;

/**
 * Serves as the highest-level object for the CDG Server application.
 * Houses and manages communication between a {@link GameServer} and a {@link FileServer}, and also manages instances of the Communicator class.
 */
public class CDGServer
{
	private static final CDGServer server = new CDGServer( );

	private static String configFileName = "config.txt";
	
	private static int socketPort;
	private static ServerSocket serverSocket;
	
	private static GameServer gameServer;
	
	private static FileServer fileServer;

	/**
	 * Private nullary constructor to ensure that this class remains a singleton 
	 */
	private CDGServer( )
	{
		System.out.println( "CDGServer( )" );
	}

	/**
	 * @return the singleton instance of the CDGServer class
	 */
	public static CDGServer getInstance( )
	{
		return server;
	}

	public static void main( String[ ] args ) throws CDGException, IOException, ClassNotFoundException, ParserConfigurationException
	{
		if( args.length != 1 )
		{
			throw new CDGException( "Unable to start CDGServer. Incorrect number of arguments." );
		}

		String dataFolder = args[0];
		String logFolder = null;
		String validatorListing = null;
		String playerListing = null;
		String rootGamesFolder = null;
		String newGameFolder = null;
		String dtdListing = null;
		
		String configFilePath = dataFolder + File.separator + configFileName;
		
		try
		{
			BufferedReader configReader = new BufferedReader( new FileReader( configFilePath ) );
		
			String portString = configReader.readLine( );
			socketPort = Integer.parseInt( portString );

			logFolder = configReader.readLine( );
			validatorListing = configReader.readLine( );
			playerListing = configReader.readLine( );
			rootGamesFolder = configReader.readLine( );
			newGameFolder = configReader.readLine( );
			dtdListing = configReader.readLine( );
			
			System.out.println( "socketPort: " + socketPort );
			System.out.println( "dataFolder: " + dataFolder );
			System.out.println( "logFolder: " + logFolder );
			System.out.println( "validatorListing: " + validatorListing );
			System.out.println( "playerListing: " + playerListing );
			System.out.println( "rootGamesFolder: " + rootGamesFolder );
			System.out.println( "newGameFolder: " + newGameFolder );
			System.out.println( "dtdListing: " + dtdListing );

			//System.exit( 0 );
			
			configReader.close( );
		}
		catch( FileNotFoundException e )
		{
			throw new CDGException( "Unable to start CDGServer. Unable to open configuration file [" + configFilePath + "].", e );
		}
		catch( IOException e )
		{
			throw new CDGException( "Unable to start CDGServer. Unable to read from configuration file [" + configFilePath + "].", e );
		}
		catch( NumberFormatException e )
		{
			throw new CDGException( "Unable to start CDGServer. Unable to parse ServerSocket port number from configuration file.", e );
		}
		
		try
		{
			serverSocket = new ServerSocket( socketPort );
		}
		catch( IOException e )
		{
			throw new CDGException( "Unable to start CDGServer. Could not initialize serverSocket @ port [" + socketPort + "]", e );
		}
		
		
		try
		{
			gameServer = new GameServer( dataFolder, logFolder, validatorListing, playerListing );
		}
		catch( CDGException | IOException | ClassNotFoundException e )
		{
			throw e;
		}
	
		try
		{
			fileServer = new FileServer( dataFolder, rootGamesFolder, newGameFolder, dtdListing );
		}
		catch( CDGException | IOException | ParserConfigurationException e )
		{
			throw e;
		}
		
		(new Thread( )
		{
			@Override
			public void run( )
			{
				while( true )
				{
					System.out.println( "Listening for connections..." );
					try
					{
						new Thread( new Communicator( serverSocket.accept( ) ) ).start( );
					}
					catch( IOException e )
					{
						e.printStackTrace();
					}
				}
				
			}
		}).start( );
		
		String input = null;
		BufferedReader br = new BufferedReader( new InputStreamReader( System.in ) );
		System.out.println( "Listening for CDGServer command:" );
		while( !(input = br.readLine( )).equals( "kill" ) )
		{
			if( input.equals( "/?" ) )
			{
				System.out.println( "Available commands:");
				System.out.println( " - registerGame <foldername>" );
				System.out.println( " - commitPlayers" );
				System.out.println( " - registerRule <ruleTyle> <qualifiedClassName>" );
				System.out.println( " - commitRules" );
				continue;
			}
			
			System.out.println( "CDGServer command: " + input );
			
			executeCommand(  input.split( " " ) );
			
			System.out.println( "Listening for CDGServer command:" );
		}
		
		System.out.println( "CDGServer terminated." );
		System.exit( 0 );
	}
	
	/**
	 * Convenience method for interpreting and executing a CDGServer command. For testing purposes only.
	 * 
	 * @param args an array of command arguments to be interpreted and executed
	 */
	private static void executeCommand( String[] args )
	{
		System.out.println( "executeCommand(" + Arrays.toString( args ) + ")" );
		
		if( args.length < 1)
		{
			System.out.println( "Too few arguments in executeCommand command [" + Arrays.toString( args ) + "]" );
			return;
		}
		
		try
		{
			switch( args[0] )
			{
				case "registerGame":
					if( args.length == 2 )
					{
						fileServer.registerGame( args[1] );
					}
					break;
				case "commitPlayers":
					if( args.length == 1 )
					{
						gameServer.commitPlayers( );
					}
					break;
				case "registerRule":
					if( args.length == 3 )
					{
						gameServer.registerValidator( args[1],	args[2] );
					}
					break;
				case "commitRules":
					if( args.length == 1 )
					{
						gameServer.commitValidators( );
					}
					break;
				default:
					throw new Exception( );	
			}
		}
		catch( Exception e )
		{
			System.out.println( "Unable to execute command [" + Arrays.toString( args ) + "]" );
			e.printStackTrace( );
		}
	}

	/**
	 * Receives an XML request from a CDG Client (via an instance of the @{Communicator} class) delegates the request to the appropriate member. 
	 * @param request the XML request to be served
	 * @return the response issued by the CDGServer member that received the request
	 */
	ArrayList< Document > serve( Document request )
	{
		NodeList childNodes = request.getChildNodes( );
		if( childNodes == null || childNodes.getLength( ) == 0 )
		{
			try
			{
				System.err.println( "CDGServer: unable to serve document[" + new String( XMLHelper.writeDocumentToBytes( request ) ) + "]. This document has no child nodes." );
			}
			catch( TransformerException | IOException e )
			{
				System.err.println( "CDGServer: unable to serve document or display. This document has no child nodes." );
			}
			return new ArrayList< Document >( );
		}
		
		String requestType =childNodes.item( 0 ).getNodeName( );
		
		ArrayList< Document > result = new ArrayList< Document >( );
		
		try
		{
			switch( requestType )
			{
				case RegisterPlayerRequest.requestTag:
					System.out.println( "CDGServer: serving register request." );
					result.add( gameServer.registerPlayer( request ) );
					break;
				case AuthenticatePlayerRequest.requestTag:
					System.out.println( "CDGServer: serving authenticate request." );
					result.add( gameServer.authenticatePlayer( request ) );
					break;
				case GameListRequest.requestTag:
					System.out.println( "CDGServer: serving gameList request." );
					result.add( fileServer.getGameList( request ) );
					break;
				case FetchGameRequest.requestTag:
					System.out.println( "CDGServer: serving fetchGame request." );
					result.addAll( fileServer.fetchGame( request ) );
					break;
				case ValidateRuleRequest.requestTag:
					System.out.println( "CDGServer: serving validate request." );
					result.add( gameServer.validateRule( request ) );
					break;
				case UpdateProgressRequest.requestTag:
					System.out.println( "CDGServer: serving update request." );
					result.add( gameServer.logProgress( request ) );
					break;
			}
		
			return result;
		
		}
		catch( IOException | SAXException | CDGParseException e )
		{
			try
			{
				System.err.println( "CDGServer: unable to serve document[" + new String( XMLHelper.writeDocumentToBytes( request ) ) + "]." );
			}
			catch( TransformerException | IOException e1 )
			{
				System.err.println( "CDGServer: unable to serve document or display. This document has no child nodes." );
			}
			e.printStackTrace( );
			return new ArrayList< Document >( );
		}
	}
}